package com.kinroy.briefreport.service.serviceimpl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.kinroy.briefreport.entity.Note;
import com.kinroy.briefreport.entity.RemindMessage;
import com.kinroy.briefreport.entity.User;
import com.kinroy.briefreport.enums.note.NoteType;
import com.kinroy.briefreport.enums.remind.RemindMsgStatus;
import com.kinroy.briefreport.mapper.NoteMapper;
import com.kinroy.briefreport.mapper.RemindMsgMapper;
import com.kinroy.briefreport.service.INoteService;
import com.kinroy.briefreport.service.IRemindMsgService;
import com.kinroy.briefreport.service.ScheduledService;
import com.kinroy.briefreport.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.stereotype.Service;

import javax.annotation.Nullable;
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.Set;

/**
 * 定时任务相关功能服务类
 *
 * @author kinroy
 */
@Service
@Slf4j
public class ScheduledServiceImpl implements ScheduledService {

    public static final String NOTE_TASK_QUEUE_NAME = "noteQueue";

    public static final String REMIND_USERID_SET = "remindUserIds";

    @Autowired
    private NoteMapper noteMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IRemindMsgService remindMsgService;

    @Autowired
    private RemindMsgMapper remindMsgMapper;


    /**
     * 从定时日志任务队列中取出即将要发布的日志id去发布
     * 同时从zset中删除已经发布了的日志id
     * 定时执行周期为：10mins/次
     */
    @Scheduled(fixedRate = 5 * 60 * 1000)
    @Override
    public void getNoteTaskToExecuteAndUpdateTaskQueue() {
        // 获取中国大陆的北京时区
        ZoneId beijingZoneId = ZoneId.of("Asia/Shanghai");

        /* log.info("getNoteTaskToExecuteAndUpdateTaskQueue定时任务执行了！-时间是" + LocalDateTime.now(beijingZoneId));*/
        //1.使用redis的pipeline来提高对redis的操作效率
        redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Nullable
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                //1.强转成StringRedisConnection，保证在数据交互时不会出现序列化乱码
                StringRedisConnection stringRedisConnection = (StringRedisConnection) redisConnection;
                /*//2.取出满足执行条件的noteTask,按照升序返回
                LocalDateTime now = LocalDateTime.now();
                //todo:获取当前时间有问题，需要处理一下
                long minScore = now.toEpochSecond(ZoneOffset.UTC);
                //kinroy 2024.1.24 修改一下逻辑，获取到noteTask的范围为（当前时间，当前时间+5min）
                long maxScore = now.plusMinutes(5).toEpochSecond(ZoneOffset.UTC);
                Set<String> noteIds = redisTemplate.zRangeByScore(NOTE_TASK_QUEUE_NAME, minScore, maxScore);*/

                ZSetOperations<String, String> stringStringZSetOperations = redisTemplate.opsForZSet();
                //2.取出满足执行条件的noteTask,按照升序返回
                LocalDateTime now = LocalDateTime.now();
                long minScore = now.toEpochSecond(ZoneOffset.UTC);
                //kinroy 2024.1.24 修改一下逻辑，获取到noteTask的范围为（当前时间，当前时间+5min）
                long maxScore = now.plusMinutes(5).toEpochSecond(ZoneOffset.UTC);
                Set<String> noteIds = stringStringZSetOperations.rangeByScore(NOTE_TASK_QUEUE_NAME, minScore, maxScore);
                stringStringZSetOperations.removeRangeByScore(NOTE_TASK_QUEUE_NAME, minScore, maxScore);

                if (noteIds.size() != 0) {
                    //3.将这些要发布的note进行发布，即去修改其对应db中的note_status为发布
                    Integer successNum = 0;
                    try {
                        successNum = noteMapper.batchScheduledPublishingNotes(noteIds);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (successNum != 0 && successNum == noteIds.size()) {
                        //表示成功批量发布，则可以删除zSet中的这部分数据
                        stringStringZSetOperations.removeRangeByScore(NOTE_TASK_QUEUE_NAME, minScore, maxScore);

                    }
                }
                //如果没有满足的noteTask则直接结束
                return null;
            }
        });
    }

    /**
     * 每天0点获取当前用户的待办任务数，并发出提醒
     *
     * @param userId
     * @return
     */
    @Override
    public Integer toDoTasksNumRemind(Long userId) {
        return null;
    }

    /**
     * 每天0点获取当前用户未发布的日报数，并发出提醒
     *
     * @param
     * @return
     */
    @Scheduled(cron = "0 0 0 * * ?")
    @Override
    public void dailyNoteRemind() {
        //kinroy 2024.1.26 修改一下bug，就是定时任务的方法是不能进行传参的，所以修改从TheadLocal中获取userId
        /*User currentUser = UserHolder.getUser();
        Long userId=currentUser.getUid();*/

        /*
         * kinroy 2024.1.27 更新业务逻辑：使用TheadLocal一旦用户退出并无法获取，或者不能对全体员工进行操作
         * 这里改用从redis中去取出所有员工的id来进行提醒，我们把要提醒的用户id放入set中存在redis中，快速获取
         * */

        SetOperations<String, String> setOperations = redisTemplate.opsForSet();

        Set<String> members = setOperations.members(REMIND_USERID_SET);

        for (String member : members) {
            //发布日期为今天作为查询条件去查询db，没有相应数据则创建提醒
            Long userId = Long.valueOf(member);
            LocalDateTime now = LocalDateTime.now(); //此时应该是0点

            //todo 抛异常了，处理一下（sql问题已解决）
            Note note = noteMapper.getNoteAfterThisTime(now, userId, NoteType.Daily.getCode());

            RemindMessage remindMessage = new RemindMessage();
            remindMessage.setMsgContent("当前日期为：" + now.toLocalDate() + " 您还未提交日报！");
            remindMessage.setMsgStatus(0); //默认未读
            remindMessage.setUserId(userId);
            remindMessage.setCreateTime(now);
            if (note == null) {
                //没有当天没有发布日报，则创建提醒
                remindMsgService.addRemindMsg(remindMessage);
            } else {
                //二次确认一下，是否为当前发布的日志
                LocalDate localDate = now.toLocalDate();
                LocalDate localDate1 = note.getPublishTime().toLocalDate();
                if (!localDate.equals(localDate1)) {
                    remindMsgService.addRemindMsg(remindMessage);
                }
            }
        }


    }

    /**
     * 每周五的17点获取当前用户未发布的周报数，并发出提醒
     *
     * @param
     * @return
     */
    @Scheduled(cron = "0 0 17 ? * FRI")
    /*@Scheduled(cron = "0 58 14 * * ?")*/
    @Override
    public void weeklyNoteRemind() {
        //kinroy 2024.1.26 修改一下bug，就是定时任务的方法是不能进行传参的，所以修改从TheadLocal中获取userId
       /* User currentUser = UserHolder.getUser();
        Long userId=currentUser.getUid();*/
        /*
         * kinroy 2024.1.27 更新业务逻辑：使用TheadLocal一旦用户退出并无法获取，或者不能对全体员工进行操作
         * 这里改用从redis中去取出所有员工的id来进行提醒，我们把要提醒的用户id放入set中存在redis中，快速获取
         * */

        SetOperations<String, String> setOperations = redisTemplate.opsForSet();

        Set<String> members = setOperations.members(REMIND_USERID_SET);

        for (String member : members) {
            Long userId = Long.valueOf(member);
            //发布日期为今天作为查询条件去查询db，没有相应数据则创建提醒
            /*LocalDate today = LocalDate.now();
            LocalDateTime now = today.atStartOfDay();

            Note note = noteMapper.getNoteAfterThisTime(now, userId, NoteType.Weekly.getCode());*/

            //kinroy 2024.1.27 修改一下判断周报是否提交的业务逻辑，因为周报是当周提交一次的
            LocalDate today = LocalDate.now();
            LocalDateTime now = today.atStartOfDay();
            //1.获取本周开始时间
            LocalDate nowDay = LocalDate.now();
            LocalDate startOfWeek = nowDay.with(DayOfWeek.MONDAY);
            LocalDateTime start = startOfWeek.atStartOfDay();
            //2.获取本周结束时间
            LocalDate nowDay1 = LocalDate.now();
            LocalDate endOfWeek = nowDay1.with(DayOfWeek.FRIDAY);
            LocalDateTime end = endOfWeek.atTime(23, 59, 59, 999999999);
            Note nodeBetweenTheTime = noteMapper.getNodeBetweenTheTime(start, end, userId, NoteType.Weekly.getCode());

            RemindMessage remindMessage = new RemindMessage();
            remindMessage.setMsgContent("当前日期为：" + now.toLocalDate() + " 您还未提交周报！");
            remindMessage.setMsgStatus(0); //默认未读
            remindMessage.setUserId(userId);
            remindMessage.setCreateTime(now);
            if (nodeBetweenTheTime == null) {
                //没有当天没有发布日报，则创建提醒
                remindMsgService.addRemindMsg(remindMessage);
            } else {
                //二次确认一下，是否为当前发布的日志
                LocalDate localDate = now.toLocalDate();
                LocalDate localDate1 = nodeBetweenTheTime.getPublishTime().toLocalDate();
                if (!localDate.equals(localDate1)) {
                    remindMsgService.addRemindMsg(remindMessage);
                }
            }
        }


    }

    /**
     * 每月的最后一周的最后一个工作日17点获取当前用户未发布的月度总结数，并发出提醒
     *
     * @param
     * @return
     */
    @Scheduled(cron = "0 0 17 ? * MON-FRI")
    @Override
    public void MonthlyNoteRemind() {
        //kinroy 2024.1.26 修改一下bug，就是定时任务的方法是不能进行传参的，所以修改从TheadLocal中获取userId
       /* User currentUser = UserHolder.getUser();
        Long userId=currentUser.getUid();*/

        // 获取当前日期
        LocalDate currentDate = LocalDate.now();

        // 计算最后一个工作日
        LocalDate lastWorkday = calculateLastWorkday(currentDate);
        // 判断当前日期是否是最后一个工作日
        if (currentDate.equals(lastWorkday)) {
            // 这里放你的定时任务逻辑
            //发布日期为今天作为查询条件去查询db，没有相应数据则创建提醒
            LocalDate today = LocalDate.now();
            LocalDateTime now = today.atStartOfDay();
            /*
             * kinroy 2024.1.27 更新业务逻辑：使用TheadLocal一旦用户退出并无法获取，或者不能对全体员工进行操作
             * 这里改用从redis中去取出所有员工的id来进行提醒，我们把要提醒的用户id放入set中存在redis中，快速获取
             * */

            SetOperations<String, String> setOperations = redisTemplate.opsForSet();

            Set<String> members = setOperations.members(REMIND_USERID_SET);

            for (String member : members) {
                Long userId = Long.valueOf(member);
                Note note = noteMapper.getNoteAfterThisTime(now, userId, NoteType.Monthly.getCode());
                RemindMessage remindMessage = new RemindMessage();
                remindMessage.setMsgContent("当前日期为：" + now.toLocalDate() + " 您还未提交月度总结！");
                remindMessage.setMsgStatus(0); //默认未读
                remindMessage.setUserId(userId);
                remindMessage.setCreateTime(now);
                if (note == null) {
                    //没有当天没有发布日报，则创建提醒
                    remindMsgService.addRemindMsg(remindMessage);
                } else {
                    //二次确认一下，是否为当前发布的日志
                    LocalDate localDate = now.toLocalDate();
                    LocalDate localDate1 = note.getPublishTime().toLocalDate();
                    if (!localDate.equals(localDate1)) {
                        remindMsgService.addRemindMsg(remindMessage);
                    }
                }
            }
        }
    }

    /**
     * 定期删除db中的已读的提醒消息
     * 周期设定为每隔两天的零点清除一次
     */
    @Scheduled(cron = "0 0 0 */2 * ?")
    @Override
    public void delReadRemindMsg() {
        LambdaQueryWrapper<RemindMessage> wrapper = new LambdaQueryWrapper<RemindMessage>();
        wrapper.eq(RemindMessage::getMsgStatus, RemindMsgStatus.READ);
        remindMsgService.remove(wrapper);
    }


    private LocalDate calculateLastWorkday(LocalDate date) {
        LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
        LocalDate lastWorkday = lastDayOfMonth;

        // 如果最后一天是周六或周日，则向前找到最后一个工作日
        while (lastWorkday.getDayOfWeek() == DayOfWeek.SATURDAY || lastWorkday.getDayOfWeek() == DayOfWeek.SUNDAY) {
            lastWorkday = lastWorkday.minusDays(1);
        }

        return lastWorkday;
    }
}
